package ctrl

/*******************************************************************************
Copyright:cloud
Author:cloudapex@126.com
Version:1.0
Date:2020-06-12
Description: GRPC连接控制器
*******************************************************************************/
import (
	"context"
	"service/conf"
	"time"

	"gitee.com/cloudapex/ulern/cto"
	"gitee.com/cloudapex/ulern/htp"
	"gitee.com/cloudapex/ulern/util"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/keepalive"
)

var GCon *grpcConn

func init() { GCon = cto.Install("MC_GConn", &grpcConn{}).(*grpcConn) }

type grpcConn struct {
	cto.ControlBase

	gCons     map[string]*grpc.ClientConn
	clienters map[string][]func(*grpc.ClientConn)
}

func (this *grpcConn) HandleInit() {
	this.ControlBase.HandleInit()

	this.gCons = make(map[string]*grpc.ClientConn)
	util.Cast(this.clienters == nil, func() { this.clienters = make(map[string][]func(*grpc.ClientConn)) }, nil)

	this.startConnect()
}
func (this *grpcConn) HandleTerm() {
	for _, c := range this.gCons {
		c.Close()
	}
	this.ControlBase.HandleTerm()
}

// -------- public
func (this *grpcConn) RegistClienter(name string, fun func(*grpc.ClientConn)) {
	util.Cast(this.clienters == nil, func() { this.clienters = make(map[string][]func(*grpc.ClientConn)) }, nil)
	this.clienters[name] = append(this.clienters[name], fun)
}

// -------- internal
func (this *grpcConn) startConnect() {
	this.Infov("Connecting to grpc server...")

	if conf.Main.ServerAddr() != "" {
		conn, err := grpc.Dial(conf.Main.ServerAddr(), this.initOptions()...)
		if err != nil {
			this.Fatalv(err)
		}
		this.gCons["only"] = conn
		for _, cfuns := range this.clienters {
			for _, cfun := range cfuns {
				cfun(conn)
			}
		}
		return
	}
	for name, con := range conf.Main.GConns() {
		if !con.Enable {
			continue
		}
		this.Tracev(name, con.Addr)
		conn, err := grpc.Dial(con.Addr, this.initOptions()...)
		if err != nil {
			this.Fatalv(err)
		}
		this.gCons[name] = conn
		for _, cfun := range this.clienters[name] {
			cfun(conn)
		}
	}
	if len(this.gCons) == 0 {
		this.Warnv("no grpc server host available")
	}
	for name, _ := range this.clienters {
		if _, ok := this.gCons[name]; !ok {
			this.Warn("no connection available for module[%q]", name)
		}
	}
}
func (this *grpcConn) initOptions() []grpc.DialOption {
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithBlock())

	// tls
	if o := conf.Main.GrpcOptTLS(); o.Enable {
		creds, err := credentials.NewClientTLSFromFile(o.CrtFile, o.SerName)
		if err != nil {
			this.Fatalv(err)
		}
		opts = append(opts, grpc.WithTransportCredentials(creds))
	} else {
		opts = append(opts, grpc.WithInsecure())
	}

	// keepalive
	if o := conf.Main.GrpcOptKep(); o.Enable {
		opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
			Time:                time.Duration(o.Time) * time.Second,
			Timeout:             time.Duration(o.Timeout) * time.Second,
			PermitWithoutStream: o.WithoutStream,
		}))
	}
	// credent(auth)
	if o := conf.Main.GrpcOptCred(); o.Enable {
		opts = append(opts, grpc.WithPerRPCCredentials(&authToken{}))
	}

	// interceptors
	opts = append(opts, grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
		this.serverIdInterceptor,
	)))

	// 负载&服务发现

	// 重试

	return opts
}
func (this *grpcConn) serverIdInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	return invoker(htp.WithGHead(ctx, "server_id", conf.Main.ServerId()), method, req, reply, cc, opts...)
}

// ---------- authToken
const headerAuthorize string = "authorization"

type authToken struct{}

func (this *authToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{headerAuthorize: conf.Main.GrpcOptCred().AuthScheme + " " + ctx.Value(htp.DEF_CLAIMS_HEAD_KEY).(string)}, nil
}

func (this *authToken) RequireTransportSecurity() bool {
	return false
}
